package mspbots.next.ticket.core.grader.interceptor;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import mspbots.next.ticket.core.grader.AbstractGradeRuleAdapter;
import mspbots.next.ticket.core.grader.GradeRuleInterceptorRegistration;
import mspbots.next.ticket.core.points.PointResult;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author Jay.Yang
 */
@RequiredArgsConstructor
@Service(value = "SCHEDULED_STATUS_POINT_RULE")
public class ScheduledStatusRuleInterceptor extends AbstractGradeRuleAdapter {

    private static final String RULE_KEY = "ScheduleStatus";
    private List<ScheduledStatusParam> params;
    private String identifier;
    private List<Schedules> schedulesList;

    @Override
    public Mono<GradeRuleInterceptorRegistration> prepare(GradeRuleInterceptorRegistration gradeRuleInterceptorRegistration) {
        var mono1 = this.r2dbcEntityTemplate.select(Schedules.class)
                .matching(Query.query(Criteria.where("tenantCode")
                        .is(gradeRuleInterceptorRegistration.getRule().getTenantId())
                        .and("identifier").is(gradeRuleInterceptorRegistration.getRule().getRequest().getIdentifier())
                        .and("doneFlag").isFalse().and("updateTime").greaterThanOrEquals(LocalDate.now().minusMonths(3))))
                .all().collectList()
                .doOnNext(re -> this.schedulesList = re);
        return Flux.concatDelayError(mono1)
                .doFinally(r -> {
                    this.params = super.objectMapper
                            .convertValue(gradeRuleInterceptorRegistration.getRule().getParams(), new TypeReference<>() {
                            });
                    this.identifier = gradeRuleInterceptorRegistration.getRule().getRequest().getIdentifier();
                })
                .then(Mono.just(gradeRuleInterceptorRegistration));
    }

    @Override
    public Mono<PointResult> scoring(JsonNode ticket) {
        AtomicInteger order = new AtomicInteger();
        Integer statusId = super.getIntValueId(ticket.get("status"));

        Schedules schedule = null;
        if (!CollectionUtils.isEmpty(this.schedulesList)) {
            boolean isAssignedSchedule = false;
            if (StringUtils.hasLength(this.identifier)) {
                isAssignedSchedule = true;
                schedule = this.schedulesList.stream()
                        .filter(s -> s.getDateStart() != null)
                        .filter(s -> StringUtils.hasLength(s.getIdentifier())
                                && Arrays.stream(StringUtils.commaDelimitedListToStringArray(s.getIdentifier()))
                                .collect(Collectors.toList()).contains(this.identifier))
                        .min(Comparator.comparing(Schedules::getDateStart)).orElse(null);
            }
            if (ObjectUtils.isEmpty(schedule)) {
                isAssignedSchedule = false;
                schedule = this.schedulesList.stream()
                        .filter(s -> s.getDateStart() != null)
                        .min(Comparator.comparing(Schedules::getDateStart)).orElse(null);
            }
            if (schedule != null) {
                Schedules finalTicketSchedule = schedule;
                boolean finalIsAssignedSchedule = isAssignedSchedule;
                params.stream().filter(item -> item.isSelected() && item.getStatusIds().contains(statusId))
                        .forEach(item -> {
                            long time = Duration.between(LocalDateTime.now(), finalTicketSchedule.getDateStart()).toMinutes();
                            if ("moreThan".equalsIgnoreCase(item.getCompareType()) && time >= item.getMinutes()) {
                                if (!StringUtils.hasLength(this.identifier) || item.getPoints() <= 0) {
                                    order.addAndGet(item.getPoints());
                                } else if (finalIsAssignedSchedule && item.getPoints() > 0) {
                                    order.addAndGet(item.getPoints());
                                }
                            } else if ("lessThan".equalsIgnoreCase(item.getCompareType()) && time <= item.getMinutes()) {
                                if (!StringUtils.hasLength(this.identifier) || item.getPoints() <= 0) {
                                    order.addAndGet(item.getPoints());
                                } else if (finalIsAssignedSchedule && item.getPoints() > 0) {
                                    order.addAndGet(item.getPoints());
                                }
                            }
                        });
            }
        }
        return Mono.just(PointResult.withDefault().point(order.get())
                .detail(PointResult.Detail.with(order.get(),
                        Map.of("scheduleId", schedule != null ? schedule.getScheduleId() : "",
                                "scheduleStartTime", schedule != null ? schedule.getDateStart().toString() : "",
                                "scheduleIdentifier", schedule != null ? schedule.getIdentifier() : ""), RULE_KEY)));
    }

    @Data
    private static class ScheduledStatusParam {
        private Integer index;
        private Integer points;
        private Integer minutes;
        private List<Integer> statusIds;
        private boolean selected;
        private String compareType;
    }

    @Data
    @Table("cw_schedules_view")
    public static class Schedules {
        @Id
        private Long id;
        private Long scheduleId;
        private Long tenantCode;
        private Long objectId;
        private Long memberId;
        private String scheduleName;
        private String identifier;
        private LocalDateTime dateStart;
        private LocalDateTime dateEnd;
        private Boolean doneFlag;
        private Long statusId;
        private String statusName;
        private LocalDateTime updateTime;
    }
}
